//
// Created by zpf on 2023/5/16.
//

#include <QHash>
#include <mutex>
#include "status-notifier-host.h"
#include "status-notifier-item.h"
#include "statusnotifierwatcher_interface.h"
#include "dbusproperties.h"

namespace UkuiSni {

static std::once_flag flag;
static StatusNotifierHost *s_self;
static const QString s_watcherServiceName(QStringLiteral("org.kde.StatusNotifierWatcher"));
class StatusNotifierHostPrivate : public QObject
{
    friend class StatusNotifierHost;
    Q_OBJECT
public:
    explicit StatusNotifierHostPrivate(StatusNotifierHost *q);
    ~StatusNotifierHostPrivate();

    void registerHost();
private:
    void serviceChange(const QString &name, const QString &oldOwner, const QString &newOwner);
    void registerHost(const QString &service);
    void unregisterHost(const QString &service);
    void itemRegistered(const QString &service);
    void itemUnregistered(const QString &service);
    void removeAllItems();

    org::kde::StatusNotifierWatcher *m_statusNotifierWatcher = nullptr;
    QString m_serviceName;
    static const int s_protocolVersion = 0;
    QHash<QString, StatusNotifierItem *> m_registeredItems;
    StatusNotifierHost *q = nullptr;
};

StatusNotifierHostPrivate::StatusNotifierHostPrivate(StatusNotifierHost *q) : QObject(q), q(q)
{
}

StatusNotifierHostPrivate::~StatusNotifierHostPrivate()
{
}

void StatusNotifierHostPrivate::registerHost()
{
    if (QDBusConnection::sessionBus().isConnected()) {
        m_serviceName = "org.kde.StatusNotifierHost-" + QString::number(QCoreApplication::applicationPid());
        QDBusConnection::sessionBus().registerService(m_serviceName);

        auto *watcher = new QDBusServiceWatcher(s_watcherServiceName,
                                                QDBusConnection::sessionBus(),
                                                QDBusServiceWatcher::WatchForOwnerChange,
                                                this);
        connect(watcher, &QDBusServiceWatcher::serviceOwnerChanged, this, &StatusNotifierHostPrivate::serviceChange);

        registerHost(s_watcherServiceName);
    }
}

void StatusNotifierHostPrivate::serviceChange(const QString &name, const QString &oldOwner, const QString &newOwner)
{
    qDebug() << "Service" << name << "status change, old owner:" << oldOwner << "new:" << newOwner;

    if (newOwner.isEmpty()) {
        unregisterHost(name);
    } else if (oldOwner.isEmpty()) {
        registerHost(name);
    }
}

void StatusNotifierHostPrivate::registerHost(const QString &service)
{
    if (service == s_watcherServiceName) {
        if(m_statusNotifierWatcher) {
            delete m_statusNotifierWatcher;
            m_statusNotifierWatcher = nullptr;
        }

        m_statusNotifierWatcher = new org::kde::StatusNotifierWatcher(s_watcherServiceName,
                                                                      QStringLiteral("/StatusNotifierWatcher"),
                                                                      QDBusConnection::sessionBus(),
                                                                      this);
        if (m_statusNotifierWatcher->isValid()) {
            m_statusNotifierWatcher->call(QDBus::NoBlock, QStringLiteral("RegisterStatusNotifierHost"), m_serviceName);
            connect(m_statusNotifierWatcher, &OrgKdeStatusNotifierWatcherInterface::StatusNotifierItemRegistered,
                    this, &StatusNotifierHostPrivate::itemRegistered);
            connect(m_statusNotifierWatcher, &OrgKdeStatusNotifierWatcherInterface::StatusNotifierItemUnregistered,
                    this, &StatusNotifierHostPrivate::itemUnregistered);

            OrgFreedesktopDBusPropertiesInterface propertiesIface(m_statusNotifierWatcher->service(),
                                                                  m_statusNotifierWatcher->path(),
                                                                  m_statusNotifierWatcher->connection());

            QDBusPendingReply<QDBusVariant> pendingItems = propertiesIface.Get(m_statusNotifierWatcher->interface(), "RegisteredStatusNotifierItems");

            auto *watcher = new QDBusPendingCallWatcher(pendingItems, this);
            connect(watcher, &QDBusPendingCallWatcher::finished, this, [&, watcher]() {
                watcher->deleteLater();
                QDBusReply<QDBusVariant> reply = *watcher;
                QStringList registeredItems = reply.value().variant().toStringList();
                        for(const QString &service : registeredItems) {
                        if (!m_registeredItems.contains(service)) {
                            itemRegistered(service);
                        }
                    }
            });
        } else {
            if(m_statusNotifierWatcher) {
                delete m_statusNotifierWatcher;
                m_statusNotifierWatcher = nullptr;
            }
            qDebug() << "Status notifier Watcher not reachable";
        }
    }
}

void StatusNotifierHostPrivate::unregisterHost(const QString &service)
{
    if (service == s_watcherServiceName) {
        removeAllItems();
        if(m_statusNotifierWatcher) {
            delete m_statusNotifierWatcher;
            m_statusNotifierWatcher = nullptr;
        }
    }
}

void StatusNotifierHostPrivate::itemRegistered(const QString &service)
{
    if(!m_registeredItems.contains(service)) {
        auto *item = new StatusNotifierItem(service);
        m_registeredItems.insert(service, item);
        Q_EMIT q->itemAdded(service);
        qDebug() << "Registering sni" << service;
    }
}

void StatusNotifierHostPrivate::itemUnregistered(const QString &service)
{
    if (m_registeredItems.contains(service)) {
        auto item = m_registeredItems.value(service);
        item->disconnect();
        item->deleteLater();
        m_registeredItems.remove(service);
        Q_EMIT q->itemRemoved(service);
    }
}

void StatusNotifierHostPrivate::removeAllItems()
{
    for(const QString &service : m_registeredItems.keys()) {
        delete m_registeredItems.value(service);
        m_registeredItems.remove(service);
        Q_EMIT q->itemRemoved(service);
    }
}

StatusNotifierHost *StatusNotifierHost::self()
{
    std::call_once(flag, [ & ] {
        s_self = new StatusNotifierHost();
    });
    return s_self;
}

StatusNotifierHost::StatusNotifierHost(QObject *parent): QObject(parent), d(new StatusNotifierHostPrivate(this))
{
}

StatusNotifierHost::~StatusNotifierHost()
{
}

QList<StatusNotifierItem *> StatusNotifierHost::items()
{
    return d->m_registeredItems.values();
}

const QList<QString> StatusNotifierHost::services() const
{
    return d->m_registeredItems.keys();
}

StatusNotifierItem *StatusNotifierHost::itemForService(const QString service)
{
    return d->m_registeredItems.value(service);
}

void StatusNotifierHost::registerHost()
{
    d->registerHost();
}
}
#include "status-notifier-host.moc"
